package com.fyl.schedule;

import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import java.lang.reflect.Method;
import java.util.List;

public class QuartzManager implements ApplicationContextAware {

    private Logger logger = LoggerFactory.getLogger(QuartzManager.class);

    private SchedulerFactoryBean schedulerFactoryBean;

    private ApplicationContext applicationContext;

    public QuartzManager(SchedulerFactoryBean schedulerFactoryBean) {
        this.schedulerFactoryBean = schedulerFactoryBean;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }


    public void startup(List<JobDefinition> jobDefinitions) {
        jobDefinitions.forEach((job) -> {
            if (job != null) {
                addJob(job);
            }
        });
        startup();
    }

    /**
     * 启动
     */
    public void startup() {
        if (!schedulerFactoryBean.isRunning()) {
            schedulerFactoryBean.start();
        }
    }

    /**
     * 关闭
     */
    public void shutdown() {
        try {
            schedulerFactoryBean.destroy();
            logger.info("quartz shutdown succeed.");
        } catch (SchedulerException e) {
            logger.error("quartz shutdown failed.", e);
        }
        logger.info("quartz shutdown finished.");
    }

    /**
     * 获取Job状态
     *
     * @param jobName  String
     * @param jobGroup String
     * @return Trigger.TriggerState
     */
    public Trigger.TriggerState getJobState(String jobName, String jobGroup) {
        TriggerKey triggerKey = new TriggerKey(jobName, jobGroup);
        try {
            return schedulerFactoryBean.getScheduler().getTriggerState(triggerKey);
        } catch (SchedulerException e) {
            logger.error(e.getMessage(), e);
            logger.error("getJobState failed jobName:{}", jobName);
            return Trigger.TriggerState.ERROR;
        }
    }


    /**
     * 获取Job状态
     *
     * @param jobName  String
     * @param jobGroup String
     * @return boolean
     */
    public boolean checkExists(String jobName, String jobGroup) {
        JobKey jobKey = new JobKey(jobName, jobGroup);
        TriggerKey triggerKey = new TriggerKey(jobName, jobGroup);
        try {
            return schedulerFactoryBean.getScheduler().checkExists(jobKey) && schedulerFactoryBean.getScheduler().checkExists(triggerKey);
        } catch (SchedulerException e) {
            logger.error(e.getMessage(), e);
            logger.error("checkExists failed jobName:{}", jobName);
            return false;
        }
    }

    /**
     * 添加Job
     *
     * @param jobDefinition JobDefinition
     * @return boolean
     */
    public boolean addJob(JobDefinition jobDefinition) {

        String jobGroup = jobDefinition.getJobGroup();
        String jobName = jobDefinition.getJobName();
        Boolean disallowConcurrentExecution = jobDefinition.getDisallowConcurrentExecution();
        String cron = jobDefinition.getCron();

        String clazz = jobDefinition.getClazz();
        String method = jobDefinition.getMethod();

        try {
            if (!CronExpression.isValidExpression(cron)) {
                logger.error("Illegal cron expression format({})", cron);
                return false;
            }

            Class<? extends Job> jobClass = disallowConcurrentExecution ? QuartzJobDisallowConcurrentExecution.class : QuartzJob.class;

            Object jobBean = this.registerBeanIfAbsent(clazz);
            Method jobMethod = BeanUtils.findDeclaredMethod(jobBean.getClass(), method);
            if (null == jobMethod) {
                jobMethod = BeanUtils.findDeclaredMethod(jobBean.getClass(), method, JobExecutionContext.class);
            }

            JobDataMap jobDataMap = new JobDataMap();
            jobDataMap.put("jobBean", jobBean);
            jobDataMap.put("jobMethod", jobMethod);

            JobDetail jobDetail = JobBuilder.newJob()
                    .withIdentity(jobName, jobGroup)
                    .ofType(jobClass)
                    .setJobData(jobDataMap)
                    .build();

            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(jobName, jobGroup)
                    .forJob(jobDetail)
                    .withSchedule(CronScheduleBuilder.cronSchedule(cron))
                    .build();

            schedulerFactoryBean.getScheduler().scheduleJob(jobDetail, trigger);

            this.startup();
            return true;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            logger.error("QuartzManager add job failed; {}", jobDefinition.toString());
            return false;
        }
    }


    /**
     * @param jobName  String
     * @param jobGroup String
     * @param cronExp  corn 表达式
     * @return boolean
     */
    public boolean updateJob(String jobName, String jobGroup, String cronExp) {
        boolean result = false;
        if (!CronExpression.isValidExpression(cronExp)) {
            logger.error("Illegal cron expression format({})", cronExp);
            return result;
        }
        JobKey jobKey = new JobKey(jobName, jobGroup);
        TriggerKey triggerKey = new TriggerKey(jobName, jobGroup);
        try {
            if (schedulerFactoryBean.getScheduler().checkExists(jobKey) && schedulerFactoryBean.getScheduler().checkExists(triggerKey)) {
                JobDetail jobDetail = schedulerFactoryBean.getScheduler().getJobDetail(jobKey);
                Trigger trigger = schedulerFactoryBean.getScheduler().getTrigger(triggerKey);
                Trigger newTrigger = TriggerBuilder.newTrigger()
                        .forJob(jobDetail)
                        .withSchedule(CronScheduleBuilder.cronSchedule(cronExp))
                        .withIdentity(triggerKey)
                        .build();
                schedulerFactoryBean.getScheduler().rescheduleJob(triggerKey, newTrigger);
                result = true;
            } else {
                logger.error("update job name:{},group name:{} or trigger name:{},group name:{} not exists..",
                        jobKey.getName(), jobKey.getGroup(), triggerKey.getName(), triggerKey.getGroup());
            }
        } catch (SchedulerException e) {
            logger.error(e.getMessage(), e);
            logger.error("update job name:{},group name:{} failed!", jobKey.getName(), jobKey.getGroup());
        }
        return result;
    }

    /**
     * 暂停 Job
     *
     * @param jobName  String
     * @param jobGroup String
     */
    public void pauseJob(String jobName, String jobGroup) {
        JobKey jobKey = new JobKey(jobName, jobGroup);
        try {
            if (schedulerFactoryBean.getScheduler().checkExists(jobKey)) {
                schedulerFactoryBean.getScheduler().pauseJob(jobKey);
            } else {
                logger.error("pause job name:{},group name:{} not exists.", jobKey.getName(), jobKey.getGroup());
            }
        } catch (SchedulerException e) {
            logger.error(e.getMessage(), e);
            logger.error("pause job name:{},group name:{} failed!", jobKey.getName(), jobKey.getGroup());
        }
    }


    /**
     * 恢复 Job
     *
     * @param jobName  String
     * @param jobGroup String
     */
    public void resumeJob(String jobName, String jobGroup) {
        JobKey jobKey = new JobKey(jobName, jobGroup);
        try {
            if (schedulerFactoryBean.getScheduler().checkExists(jobKey)) {
                schedulerFactoryBean.getScheduler().resumeJob(jobKey);
            } else {
                logger.error("pause job name:{},group name:{} not exists.", jobKey.getName(), jobKey.getGroup());
            }
        } catch (SchedulerException e) {
            logger.error(e.getMessage(), e);
            logger.error("pause job name:{},group name:{} failed!", jobKey.getName(), jobKey.getGroup());
        }
    }

    /**
     * 删除 Job
     *
     * @param jobName  String
     * @param jobGroup String
     */
    public boolean deleteJob(String jobName, String jobGroup) {
        boolean result = false;
        JobKey jobKey = new JobKey(jobName, jobGroup);
        try {
            if (schedulerFactoryBean.getScheduler().checkExists(jobKey)) {
                result = schedulerFactoryBean.getScheduler().deleteJob(jobKey);
            } else {
                logger.error("delete job name:{},group name:{} not exists.", jobKey.getName(), jobKey.getGroup());
            }
        } catch (SchedulerException e) {
            logger.error(e.getMessage(), e);
            logger.error("delete job name:{},group name:{} failed!", jobKey.getName(), jobKey.getGroup());
        }
        return result;
    }


    private Object registerBeanIfAbsent(String clazz) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        Class<?> clz = Class.forName(clazz);
        Object bean = null;
        try {
            bean = this.applicationContext.getBean(clz);
        } catch (Exception e) {
            //todo
        }
        if (null != bean) {
            return bean;
        }
        bean = clz.newInstance();

        //将applicationContext转换为ConfigurableApplicationContext
        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) this.applicationContext;
        // 获取bean工厂并转换为DefaultListableBeanFactory
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();

        // 属性自动注入 BeanFactory
        AutowireCapableBeanFactory autowireCapableBeanFactory = configurableApplicationContext.getAutowireCapableBeanFactory();

        // 注入到容器中
        String springId = this.toLowerCaseFirstOne(clz.getSimpleName());
        defaultListableBeanFactory.registerSingleton(springId, bean);
        // 属性注入
        autowireCapableBeanFactory.autowireBean(bean);
        return bean;

    }


    //首字母转小写
    private String toLowerCaseFirstOne(String s) {
        if (Character.isLowerCase(s.charAt(0)))
            return s;
        else {
            return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
        }
    }

}
